use crate::{
    object::{DrawableObj, Object},
    v3f,
    vector::{Vector2f, Vector3f},
};

pub struct MeshTriangle {
    pub obj_attr: Object,
    pub vertices: Box<Vec<Vector3f>>,
    pub num_triangles: usize,
    pub vertex_index: Box<Vec<usize>>,
    pub st_coordinates: Box<Vec<Vector2f>>,
}

impl MeshTriangle {
    pub fn new(
        verts: Vec<Vector3f>,
        verts_index: Vec<usize>,
        num_tris: usize,
        st: Vec<Vector2f>,
    ) -> Self {
        // let mut max_index: usize = 0;
        // for i in 0..num_tris * 3 {
        //     if verts_index[i] > max_index {
        //         max_index = verts_index[i];
        //     }
        // }
        // max_index += 1;

        Self {
            vertices: Box::new(verts),
            num_triangles: num_tris,
            vertex_index: Box::new(verts_index),
            st_coordinates: Box::new(st),
            obj_attr: Object {
                ..Default::default()
            },
        }
    }

    fn ray_triangle_intersect(
        v0: Vector3f,
        v1: Vector3f,
        v2: Vector3f,
        orig: Vector3f,
        dir: Vector3f,
        tnear: &mut f32,
        u: &mut f32,
        v: &mut f32,
    ) -> bool {
        let s = orig - v0;
        let e1 = v1 - v0;
        let e2 = v2 - v0;
        let s1 = dir.cross_product(&e2);
        let s2 = s.cross_product(&e1);

        let s1e1 = s1.dot_product(&e1);
        let t = s2.dot_product(&e2) / s1e1;
        let b1 = s1.dot_product(&s) / s1e1;
        let b2 = s2.dot_product(&dir) / s1e1;

        if t >= 0. && b1 >= 0. && b2 >= 0. && (1.0 - b1 - b2) >= 0. {
            *tnear = t;
            *u = b1;
            *v = b2;
            true
        } else {
            false
        }
    }
}

impl DrawableObj for MeshTriangle {
    fn intersect(
        &self,
        orig: Vector3f,
        dir: Vector3f,
        tnear: &mut f32,
        index: &mut u32,
        uv: &mut Vector2f,
    ) -> bool {
        let mut intersect = false;
        for i in 0..self.num_triangles {
            let v0: &Vector3f = &self.vertices[self.vertex_index[i * 3]];
            let v1: &Vector3f = &self.vertices[self.vertex_index[i * 3 + 1]];
            let v2: &Vector3f = &self.vertices[self.vertex_index[i * 3 + 2]];
            let mut t: f32 = 0.0;
            let mut u: f32 = 0.0;
            let mut v: f32 = 0.0;
            if Self::ray_triangle_intersect(*v0, *v1, *v2, orig, dir, &mut t, &mut u, &mut v) {
                *tnear = t;
                uv.x = u;
                uv.y = v;
                *index = i as u32;
                intersect |= true;
            }
        }

        intersect
    }

    fn eval_diffuse_color(&self, st: Vector2f) -> Vector3f {
        let scale = 5.;
        let pattern = ((st.x * scale % 1.0) > 0.5) ^ ((st.y * scale % 1.0) > 0.5);
        let pattern = if pattern { 1. } else { 0. };
        Vector3f::lerp(
            v3f!(0.815, 0.235, 0.031),
            v3f!(0.937, 0.937, 0.231),
            pattern,
        )
    }

    fn get_surface_properties(
        &self,
        _hit_point: Vector3f,
        _dir: Vector3f,
        index: u32,
        uv: Vector2f,
        n: &mut Vector3f,
        st: &mut Vector2f,
    ) {
        let v0: &Vector3f = &self.vertices[self.vertex_index[index as usize * 3]];
        let v1: &Vector3f = &self.vertices[self.vertex_index[index as usize * 3 + 1]];
        let v2: &Vector3f = &self.vertices[self.vertex_index[index as usize * 3 + 2]];
        let e0 = (*v1 - *v0).normalize();
        let e1 = (*v2 - *v1).normalize();

        *n = e0.cross_product(&e1).normalize();
        let st0 = self.st_coordinates[self.vertex_index[index as usize * 3]];
        let st1 = self.st_coordinates[self.vertex_index[index as usize * 3 + 1]];
        let st2 = self.st_coordinates[self.vertex_index[index as usize * 3 + 2]];

        *st = st0 * (1.0 - uv.x - uv.y) + st1 * uv.x + st2 * uv.y;
    }

    fn get_material_type(&self) -> crate::global::MaterialType {
        self.obj_attr.material_type
    }

    fn get_ior(&self) -> f32 {
        self.obj_attr.ior
    }
    fn get_specular_exponent(&self) -> f32 {
        self.obj_attr.specular_exponent
    }
    fn get_kd(&self) -> f32 {
        self.obj_attr.kd
    }
    fn get_ks(&self) -> f32 {
        self.obj_attr.ks
    }
}
